package net.ericaro.neojavagengen;

import java.beans.Introspector;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.ericaro.neojavagen.Literals;
import net.ericaro.neojavagen.blocks.BlockStatements;
import net.ericaro.neojavagen.classes.ClassBody;
import net.ericaro.neojavagen.classes.ClassBodyDeclarations;
import net.ericaro.neojavagen.classes.ClassModifier;
import net.ericaro.neojavagen.classes.ClassModifiers;
import net.ericaro.neojavagen.classes.ConstructorBody;
import net.ericaro.neojavagen.classes.ConstructorDeclaration;
import net.ericaro.neojavagen.classes.ConstructorDeclarator;
import net.ericaro.neojavagen.classes.ConstructorModifier;
import net.ericaro.neojavagen.classes.ConstructorModifiers;
import net.ericaro.neojavagen.classes.FormalParameter;
import net.ericaro.neojavagen.classes.FormalParameters;
import net.ericaro.neojavagen.classes.InterfaceTypes;
import net.ericaro.neojavagen.classes.Interfaces;
import net.ericaro.neojavagen.classes.NormalClassDeclaration;
import net.ericaro.neojavagen.classes.Super;
import net.ericaro.neojavagen.classes.SuperExplicitConstructorInvocation;
import net.ericaro.neojavagen.classes.ThisExplicitConstructorInvocation;
import net.ericaro.neojavagen.classes.VariableDeclaratorId;
import net.ericaro.neojavagen.expressions.Arguments;
import net.ericaro.neojavagen.expressions.QuestionMarkConditionalExpression;
import net.ericaro.neojavagen.expressions.UnaryExpression;
import net.ericaro.neojavagen.packages.TypeDeclaration;
import net.ericaro.neojavagen.types.ClassOrInterfaceType;
import net.ericaro.neojavagen.types.ClassType;
import net.ericaro.neojavagen.types.Identifier;
import net.ericaro.neojavagen.types.Type;
import net.ericaro.neojavagen.types.TypedIdentifier;

public class MetaComposition extends Meta {

	/**
	 * any part of a composition
	 * 
	 * @author eric
	 */
	private abstract class Component {
		String name;
		boolean optional;
		boolean literal;
		Type type;
		Identifier field;
		UnaryExpression value;
		UnaryExpression option;

		abstract void init();
	}

	/**
	 * another Meta stuff
	 * 
	 * @author eric
	 */
	private class MetaComponent extends Component {
		private MetaType target;

		public MetaComponent(MetaType target, boolean optional) {
			super();
			this.literal = false;
			this.target = target;
			this.optional = optional;
			this.name = Introspector.decapitalize(target.getNameIdentifier().toString());
		}

		void init() {
			this.type = target.getClassOrInterfaceType();
			this.option = Literals.nullLiteral();
			this.field = Grammar.nameSymbol(name);
		}

	}

	/**
	 * a literal part
	 * 
	 * @author eric
	 */
	private class Literal extends Component {
		String symbol;

		public Literal(String value, boolean optional) {
			super();
			this.literal = true;
			this.optional = optional;
			this.symbol = value;
			this.name = optional ? symbol : null;
		}

		void init() {
			this.value = Literals.stringLiteral(symbol);
			if (optional) {
				this.type = Grammar.fromJava(Boolean.TYPE);
				this.field = Grammar.nameSymbol(name);
				this.option = Literals.booleanLiteral(false);
			}
		}
	}

	private List<Component> components;

	public MetaComposition(Grammar g, String name) {
		super(g, name);
		this.components = new ArrayList<Component>();
	}

	public MetaComposition withComponent(String name, boolean optional) {
		components.add(new MetaComponent(grammar.createMetaType(name), optional));
		return this;
	}

	public MetaComposition withLiteral(String value, boolean optional) {
		components.add(new Literal(value, optional));

		return this;
	}

	@Override
	protected TypeDeclaration getTypeDeclaration() {

		ClassBodyDeclarations classBodyDeclarations = new ClassBodyDeclarations();

		// workaround, type declaration sadly handles null interfaces, but not empty ones (it generates an "implements" followed by nothing
		InterfaceTypes interfaceTypes = getMetaType().getInterfaceTypes();
		Interfaces interfaces;
		if (interfaceTypes.size() == 0)
			interfaces = null;
		else
			interfaces = new Interfaces(interfaceTypes);

		// generate fields for optional values
		Map<String, Integer> used = new HashMap<String, Integer>(); // count occurences, to rename fields
		for (Component c : components) {
			if( self.name.equals("BasicForStatement") )
				System.out.println(self.name+":"+  c.name+" "+c.literal+" "+c.optional);
			if (used.containsKey(c.name)) {
				int i = used.get(c.name);
				used.put(c.name, i + 1);
				c.name += i;
			} else
				used.put(c.name, 1);
			c.init();
		}

		boolean anyOptional = false; // tells wether or not I need two constructors one with non optional only, one with optionals too
		for (Component c : components)
			if (c.optional)
				anyOptional = true;

		//
		// for (Component c : components) {
		// if (c.optional || !c.literal)
		// classBodyDeclarations.add(new FieldDeclaration(null, null, new FieldModifiers(FieldModifier.APRIVATE), c.type, new VariableDeclarators(new
		// SimpleVariableDeclaratorId(new VariableDeclaratorId(c.field)))));
		//
		// }

		// add classbody content (constructors, and later more methods)
		// two kind of constructor, the minimalistic, and the maximalistic (if there are differences)

		if (anyOptional) {
			// minimalistic only non optional non literal constructor that delegates to the standard one
			FormalParameters formalparameters = new FormalParameters();

			BlockStatements blockstatements = new BlockStatements();
			Arguments arguments = new Arguments();
			ThisExplicitConstructorInvocation thisInvocation = new ThisExplicitConstructorInvocation(null, arguments);
			classBodyDeclarations.add(new ConstructorDeclaration(null, null, new ConstructorModifiers(ConstructorModifier.PUBLIC), new ConstructorDeclarator(null, getMetaType().getNameIdentifier(), formalparameters), null, new ConstructorBody(thisInvocation, blockstatements)));

			for (Component c : components) {
				if (!(c.literal || c.optional)) {// minimal list
					formalparameters.add(new FormalParameter(c.type, new VariableDeclaratorId(c.field)));
					arguments.add(Grammar.unaryExpression(c.field)); //
				}
				else if (!c.literal || c.optional) {// maximal list, in the this() method
					//switch 
					arguments.add(Grammar.unaryExpression(c.option)); //
				}

			}
		}
		// always add the maximum size constructor
		{
			FormalParameters formalparameters = new FormalParameters();
			BlockStatements blockstatements = new BlockStatements();
			Arguments arguments = new Arguments();
			SuperExplicitConstructorInvocation superInvocation = new SuperExplicitConstructorInvocation(null, arguments);
			classBodyDeclarations.add(new ConstructorDeclaration(null, null, new ConstructorModifiers(ConstructorModifier.PUBLIC), new ConstructorDeclarator(null, getMetaType().getNameIdentifier(), formalparameters), null, new ConstructorBody(superInvocation, blockstatements)));

			for (Component c : components) {
				if (!c.literal || c.optional)  {// maximal list
					formalparameters.add(new FormalParameter(c.type, new VariableDeclaratorId(c.field)));
					if (c.literal){
						QuestionMarkConditionalExpression v = new QuestionMarkConditionalExpression(Grammar.unaryExpression(c.field), Grammar.unaryExpression(c.value), Grammar.unaryExpression(Literals.nullLiteral()));
						arguments.add(v); //
					}
					else
						arguments.add(Grammar.unaryExpression(c.field)); //
				}
				else					// switch between literal or field
					arguments.add(Grammar.unaryExpression(c.value)); //
				
			}
		}
		ClassOrInterfaceType javaPartComposition = new ClassOrInterfaceType();
		for (String i : "net/ericaro/neojavagen/JavaPartComposition".split("/"))
			javaPartComposition.add(new TypedIdentifier( Literals.identifier(i)));
		
		//ClassType javaPartComposition = Grammar.fromJava(JavaPartComposition.class);
		return new NormalClassDeclaration(null, new ClassModifiers(ClassModifier.PUBLIC), getMetaType().getNameIdentifier(), null, new Super(javaPartComposition), interfaces, new ClassBody(classBodyDeclarations));
	}

}
